﻿using System;
using System.Collections.Generic;
using Jay.Sc2.Bof.Lib.Action;
using Jay.Sc2.Bof.Lib.Tools;

namespace Jay.Sc2.Bof.Lib.State
{
	public abstract class State : IState
	{
		public abstract class StateBuilding : IBuilding
		{
			private int m_Count;
			public int Count { get { return m_Count; } set { m_Count = value; } }
			private int m_Building;
			public int Building { get { return m_Building; } set { m_Building = value; } }
			public int Total { get { return Count + Building; } }
			public void Built() { Building--; Count++; }
			public virtual int Available { get { return m_Count; } }
		}
		public abstract class StateUpgrade : IUpgrade
		{
			private bool m_Available;
			public bool Available { get { return m_Available; } set { m_Available = value; } }
			private bool m_Researching;
			public bool Researching { get { return m_Researching; } set { m_Researching = value; } }
			public bool Have { get { return Available || Researching; } }
			public void Researched()
			{
				Available = true;
				Researching = false;
			}
		}

		protected State(IRace race) { m_Race = race; }
		private IRace m_Race;
		public IRace Race { get { return m_Race; } }
		private int m_Time;
		public int Time { get { return m_Time; } set { m_Time = value; } }
		private double m_Minerals;
		public double Minerals { get { return m_Minerals; } set { m_Minerals = value; } }
		private double m_Gas;
		public double Gas { get { return m_Gas; } set { m_Gas = value; } }
		private int m_WorkersOnMinerals = 0;
		public int WorkersOnMinerals { get { return m_WorkersOnMinerals; } set { m_WorkersOnMinerals = value; } }
		private int m_WorkerOnGas = 0;
		public int WorkersOnGas { get { return m_WorkerOnGas; } set { m_WorkerOnGas = value; } }
		private int m_Supply;
		public int Supply { get { return Math.Min(200, m_Supply); } set { m_Supply = value; } }
		public int m_SupplyBuilding;
		public virtual int SupplyBuilding { get { return Supply == 200 ? 0 : Math.Min(200 - Supply, m_SupplyBuilding); } }
		private int m_Food;
		public int Food { get { return m_Food; } set { m_Food = value; } }
		private int m_ScoutsSent;
		public int ScoutsSent { get { return m_ScoutsSent; } set { m_ScoutsSent = value; } }

		public bool HaveSupply(int count) { return count == 0 || Supply >= Food + count; }

		public virtual int MineralsIncome { get { return IncomeUtil.GetMineralsIncome(WorkersOnMinerals, 0, MineralPatches); } }
		public virtual int GasIncome { get { return IncomeUtil.GetGasIncome(WorkersOnGas, GasBuilding.Count); } }
		public virtual int MineralPatches { get { return Bases.Count * 8; } }

		public abstract IBuilding Bases { get; }
		public abstract IBuilding GasBuilding { get; }
		public abstract int Army { get; }
		public abstract int Workers { get; set; }
		public abstract double Score(IState state);

		public virtual void IncrementTime()
		{
			m_Time++;
			m_Gas += GasIncome / (double)60;
			m_Minerals += MineralsIncome / (double)60;
		}



		public abstract IList<IAction> NeededActions { get; }
		public abstract IActionList BasicBuildOrder { get; }


		public static int CountAction(IList<IAction> list, IAction action)
		{
			int total = 0;
			for (int c = 0; c < list.Count; c++)
				if (list[c] == action)
					total++;
			return total;
		}
		protected void AddNeededAction(IList<IAction> list, int count, params IAction[] actions)
		{
			if (count <= 0) return;
			foreach (IAction action in actions)
			{
				int actual = CountAction(list, action);
				while (actual < count)
				{
					actual++;
					list.Add(action);
				}
			}
		}
		protected void AddNeededAction(IList<IAction> list, IBuilding building, params IAction[] actions) { AddNeededAction(list, building.Count, actions); }
		protected void AddNeededAction(IList<IAction> list, IUpgrade upgrade, params IAction[] actions) { AddNeededAction(list, upgrade.Have ? 1 : 0, actions); }
		protected void AddNeededActionWithRequirements(IActionList actionList, int count, params IAction[] actions)
		{
			if(count <= 0) return;
			foreach (IAction action in actions)
			{
				int actual = actionList.CountAction(action);
				while (actual < count)
				{
					actual++;
					actionList.AddAction(action);
				}
			}
		}
		protected void AddNeededActionWithRequirements(IActionList actionList, IBuilding building, params IAction[] actions) { AddNeededActionWithRequirements(actionList, building.Count, actions); }
		protected void AddNeededActionWithRequirements(IActionList actionList, IUpgrade upgrade, params IAction[] actions) { AddNeededActionWithRequirements(actionList, upgrade.Have ? 1 : 0, actions); }

		private bool CheckGoals(ref double  score, ref int s, int c, IActionList al, IGoal goal, int count, BuildOrder bo)
		{
			bool satisfying = true;
			bool failed = false;
			for (; s < goal.Count; s++)
			{
				IState state = goal[s];
				satisfying = IsSatisfying(state);
				failed = !satisfying && ((state.Time != 0 && state.Time <= Time) || count >= m_FailedCount);
				if (!satisfying && !failed) return false;
				score += Score(state);
				if (failed) break;
			}
			bo.Score = score;
			if (!failed)
				al.Cut(c + 1);
			return true;
		}

		private List<Scouting> m_Scouting = new List<Scouting>();
		public void CheckForScouting(IBuildOrder bo, IList<Scouting> scouting)
		{
			for (int c = 0; c < scouting.Count;c++ )
			{
				Scouting scout = scouting[c];
				if (scout.LeaveNow(this))
				{
					IAction action = ActionSendScout.Instance;
					if (action.CanExecute(bo))
					{
						bo.AddAction(bo.State.Time, action);
						scouting.RemoveAt(c);
						m_Scouting.Add(scout);
						c--;
						action.Execute(bo);
					}
				}
			}
			for (int c = 0; c < m_Scouting.Count; c++)
			{
				Scouting scout = m_Scouting[c];
				if (scout.EndNow(this))
				{
					IAction action = null;
					if(scout.Died)
						action = ActionDiedScout.Instance;
					else 
						action = ActionReturnedScout.Instance;
					if (action.CanExecute(bo))
					{
						bo.AddAction(bo.State.Time, action);
						m_Scouting.RemoveAt(c);
						c--;
						action.Execute(bo);
					}
				}
			}
		}

		public int FailedCount { get { return m_FailedCount; } set { m_FailedCount = value; } }
		private int m_FailedCount = 300;
		private void TryAutoActions(IList<IAction> autoActions, IAction action, IBuildOrder bo, IActionList al, ref int c)
		{
			if (autoActions != null)
				foreach (IAction autoAction in autoActions)
					if (autoAction != action && autoAction.CanExecute(bo))
					{
						al.Insert(c, autoAction);
						c++;
						autoAction.Execute(bo);
						bo.AddAction(Time, autoAction);
					}
		}
		public IBuildOrder ExecuteActionList(IActionList al, IGoal goal, IList<IAction> autoActions, IList<Scouting> scouting)
		{
			if (goal == null || goal.Count == 0) throw new Exception();
			BuildOrder bo = new BuildOrder(Race, this, goal, al);
			bool invalid = false;
			int s = 0;
			int count = 0;
			double score = 0.0;
			for (int c = 0; c < al.Count; c++)
			{
				IAction action = al[c];
				count = 0;
				invalid = false;
				while (!action.CanExecute(bo) && !invalid)
				{
					if (action.IsInvalid(bo))
						invalid = true;
					else
					{
						TryAutoActions(autoActions, action, bo, al, ref c);
						count++;
						if (count > m_FailedCount) throw new Exception();
						IncrementTime();
						ICollection<IAction> actions = bo.GetActions(Time);
						if (actions != null)
							foreach (IAction auto in actions)
								auto.Execute(bo);
						CheckForScouting(bo, scouting);
						if (CheckGoals(ref score, ref s, c - 1, al, goal, count, bo))
							return bo;
					}
				}
				if (!invalid)
				{
					action.Execute(bo);
					bo.AddAction(Time, action);
					CheckForScouting(bo, scouting);
					if (CheckGoals(ref score, ref s, c, al, goal, 0, bo))
						return bo;
					c++;
					TryAutoActions(autoActions, action, bo, al, ref c);
					c--;
				}
				else
				{
					al.RemoveAt(c);
					c--;
				}
			}
			count = 0;
			do
			{
				IncrementTime();
				ICollection<IAction> actions = bo.GetActions(Time);
				if (actions != null)
					foreach (IAction auto in new ActionList(m_Race, actions))
						auto.Execute(bo);
				CheckForScouting(bo, scouting);
				count++;
			}
			while (!CheckGoals(ref score, ref s, al.Count, al, goal, count, bo));
			return bo;
		}

		public abstract bool IsSatisfying(IState goal);
		public abstract IState Clone();

		protected double Score(IBuilding current, IBuilding destination, double value) { return Score(current.Count, destination.Count, value); }
		protected double Score(IUpgrade current, IUpgrade destination, double value) { return Score(current.Available, destination.Available, value); }
		protected double Score(bool current, bool destination, double value) { return Score(current ? 1 : 0, destination ? 1 : 0, value); }
		protected double Score(int current, int destination, double value) { return Math.Min(current, destination) * value; }
	}
}
